package optimizers;

import java.util.PriorityQueue;

import Utilities.Utils;

import search_algorithms.LocalSearcher;
import classifiers.Classifier;
import data_structures.ConfigurationAccuracyPair;
import weka.core.Instances;

/**
 * The main algorithm of this application.
 * 
 * The algorithm itself has hyper-parameters as follows:
 * 
 * Level 0: initialConfsToTry, topKs[0], portions[0] Level 1: topKs[1],
 * portions[1] Level 2: topKs[2], portions[2] Level 3: topKs[3], portions[3] ...
 * 
 * In 0th level it performs a local search. It makes initialConfsToTry
 * iterations (in other words, tries initialConfsToTry hyper-parameter
 * configurations) using the portions[0] portion of the entire dataset. And
 * return the topKs[0] best configurations.
 * 
 * topKs[0] best configurations found in the 0th level is passed to the 1st
 * level. In the 1st level, it measures the accuracy of every configuration
 * using the portions[1] portion of the entire dataset. And return topKs[1] best
 * configurations.
 * 
 * topKs[1] best configuration found in the 1st level is passed to the 2nd level
 * ...
 */
public class HierarchicalHPOptimizer implements HPOptimizer {
	private Instances entireData;
	private Classifier classifier;
	private LocalSearcher localSearcher;
	private int initialConfsToTry;
	private int[] topKs;
	private double[] portions;

	/**
	 * @param entireData
	 *            Entire dataset.
	 * @param classifier
	 *            The classifier that is tested.
	 * @param localSearcher
	 *            The local searcher that is used in level 0.
	 * @param initialConfsToTry
	 *            See the class description.
	 * @param topKs
	 *            See the class description.
	 * @param portions
	 *            See the class description.
	 */
	public HierarchicalHPOptimizer(Instances entireData, Classifier classifier,
			LocalSearcher localSearcher, int initialConfsToTry, int[] topKs,
			double[] portions) {
		this.entireData = entireData;
		this.classifier = classifier;
		this.localSearcher = localSearcher;
		this.initialConfsToTry = initialConfsToTry;
		this.topKs = topKs;
		this.portions = portions;
	}

	public PriorityQueue<ConfigurationAccuracyPair> optimize() {

		if (topKs.length <= 0 || (topKs.length != portions.length)) {
			System.err
					.println("Number of configurations to try should be equal to the number of portions and both should be greater than 0");
			System.exit(1);
		}

		PriorityQueue<ConfigurationAccuracyPair> topKConfigurations = localSearcher
				.optimize(classifier, entireData, portions[0],
						initialConfsToTry, topKs[0]);

		PriorityQueue<ConfigurationAccuracyPair> prevLevelTopKConfigurations = topKConfigurations;
		for (int i = 1; i < topKs.length; ++i) {
			int topK = topKs[i];
			double portion = portions[i];

			PriorityQueue<ConfigurationAccuracyPair> curlevelTopKConfigurations = new PriorityQueue<ConfigurationAccuracyPair>();
			while (prevLevelTopKConfigurations.size() > 0) {
				ConfigurationAccuracyPair pair = prevLevelTopKConfigurations
						.poll();
				Instances instances = Utils.getInstancesPortion(entireData,
						portion);
				double acc = classifier.evaluateClassifier(instances,
						pair.getConf());
				curlevelTopKConfigurations.add(new ConfigurationAccuracyPair(
						pair.getConf(), acc));

				if (curlevelTopKConfigurations.size() > topK) {
					curlevelTopKConfigurations.poll();
				}
			}
			prevLevelTopKConfigurations = curlevelTopKConfigurations;
		}

		return prevLevelTopKConfigurations;
	}
}
